import * as React from "react";
import * as classNames from "classnames";
import * as _ from "lodash"

import nodeShape from "./nodeShape";
import TreeNode from "./TreeNode";
import * as shortid from 'shortid';
const PropTypes = React.PropTypes;
class Tree extends React.PureComponent<any, any> {
  static propTypes = {
    nodes: PropTypes.arrayOf(nodeShape).isRequired,
    checked: PropTypes.arrayOf(PropTypes.string),
    disabled: PropTypes.bool,
    expandDisabled: PropTypes.bool,
    expanded: PropTypes.arrayOf(PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number,
    ])),
    name: PropTypes.string,
    nameAsArray: PropTypes.bool,
    noCascade: PropTypes.bool,
    optimisticToggle: PropTypes.bool,
    clickExpand: PropTypes.bool,
    showNodeIcon: PropTypes.bool,
    onCheck: PropTypes.func,
    onExpand: PropTypes.func,
    onSetCurNode: PropTypes.func
  };


  static defaultProps = {
    checked: [],
    disabled: false,
    expandDisabled: false,
    expanded: [],
    name: undefined,
    nameAsArray: false,
    noCascade: false,
    optimisticToggle: true,
    showNodeIcon: true,
    clickExpand: false,
    propsMap: { title: "title", value: "value" },
    onCheck: () => { },
    onExpand: () => { },
    onSetCurNode: () => { },
    getExtraNode: () => { }
  };

  private nodes;
  private id: string;

  constructor(props) {
    super(props);
    this.state = {
      curNode: {}
    }

    this.id = `tree-${shortid.generate()}`;
    this.nodes = {};
    this.flattenNodes(props.nodes);
    this.unserializeLists({
      checked: props.checked,
      expanded: props.expanded,
    });

    this.onExpand = this.onExpand.bind(this);
    this.onClickLabel = this.onClickLabel.bind(this);
    this.getExtraNode = this.getExtraNode.bind(this);
    this.onFinishedEdit = this.onFinishedEdit.bind(this)
  }


  componentWillReceiveProps({ nodes, checked, expanded }) {
    if (!_.isEqual(this.props.nodes, nodes)) {
      this.flattenNodes(nodes);
    }
    this.unserializeLists({ checked, expanded });
  }



  serializeList(key) {
    const list = [];

    Object.keys(this.nodes).forEach((value) => {
      if (this.nodes[value][key]) {
        list.push(value);
      }
    });

    return list;
  }

  unserializeLists(lists) {
    // Reset values to false
    Object.keys(this.nodes).forEach((value) => {
      Object.keys(lists).forEach((listKey) => {
        this.nodes[value][listKey] = false;
      });
    });

    // Unserialize values and set their nodes to true
    Object.keys(lists).forEach((listKey) => {
      lists[listKey].forEach((value) => {
        this.nodes[value][listKey] = true;
      });
    });
  }

  flattenNodes(nodes) {
    if (!Array.isArray(nodes) || nodes.length === 0) {
      return;
    }

    nodes.forEach((node) => {
      this.nodes[node.value] = {};
      this.flattenNodes(node.children);
    });
  }

  onExpand(node) {
    const { onExpand } = this.props;

    this.toggleNode('expanded', node, node.expanded);
    onExpand(this.serializeList('expanded'));
  }

  setCurNode(node) {
    const { onSetCurNode, onUnsetCurNode } = this.props;
    const { curNode } = this.state;
    if (this.state.curNode.value != node.value) {
      this.setState({
        curNode: node,
      })
      onSetCurNode(node);
    }
  }

  getExtraNode(state) {
    const { getExtraNode } = this.props;
    return getExtraNode(state)
  }

  toggleNode(key, node, toggleValue) {
    this.nodes[node.value][key] = toggleValue;
  }

  renderChildNodes(node, level) {
    if (node.children !== null && node.expanded) {
      return this.renderTreeNodes(node.children, level + 1);
    }

    return null;
  }

  onClickLabel(node) {
    this.setCurNode(node);
    if (this.props.clickExpand) {
      this.onExpand({
        value: node.value,
        expanded: !node.expand
      });
    }
  }

  onFinishedEdit(node) {
    this.props.onFinishedEdit(node)
  }

  renderTreeNodes(nodes, level = 0) {
    const { disabled, expandDisabled, noCascade, optimisticToggle, showNodeIcon, editable } = this.props;
    const treeNodes = nodes.map((node, index) => {
      const key = `${node.value}`;
      // const checked = this.getCheckState(node, noCascade);
      const children = this.renderChildNodes(node, level);

      return (
        <TreeNode
          level={level}
          lastest={index == nodes.length - 1}
          key={key}
          selected={node.selected}
          editable={_.includes(editable, node.value)}
          // checked={false}
          className={node.className}
          disabled={disabled}
          expandDisabled={expandDisabled}
          expanded={node.expanded}
          icon={node.icon}
          label={node.label}
          // optimisticToggle={optimisticToggle}
          rawChildren={node.children}
          showNodeIcon={showNodeIcon}
          treeId={this.id}
          value={node.value}
          // onCheck={this.onCheck}
          onExpand={this.onExpand}
          onClickLabel={this.onClickLabel}
          getExtraNode={this.getExtraNode}
          onFinishedEdit={this.onFinishedEdit}
        >
          {children}
        </TreeNode>
      );
    });

    return treeNodes;
  }

  getFormattedNodes(nodes) {
    return nodes.map((node) => {
      const formatted = { ...node };

      formatted.checked = this.nodes[node.value].checked;
      formatted.expanded = this.nodes[node.value].expanded;
      formatted.selected = this.state.curNode.value == node.value;

      if (Array.isArray(node.children) && node.children.length > 0) {
        formatted.children = this.getFormattedNodes(formatted.children);
      } else {
        formatted.children = null;
      }

      return formatted;
    });
  }



  render() {
    const nodes = this.getFormattedNodes(this.props.nodes);
    const treeNodes = this.renderTreeNodes(nodes);

    const className = classNames({
      'ztree': true
    });

    return (<ul style={{ userSelect: 'none' }} className={className}>
      {treeNodes}
    </ul>);
  }
}

export default Tree;